/**
 * @file avos/pt.h
 * This header contains protothread routines.
 */

#ifndef __AVOS_PT_H_
#define __AVOS_PT_H_

/**
 * Protothread state. 
 * A protothread can have on of the following states: it can be either blocked, or ended.
 * When blocked it will continue it's execution on the next pt_schedule() call.
 */
typedef enum {
	PT_STATE_BLOCKED, /** This stage indicates that a thread is yielded */
	PT_STATE_ENDED, /** This stage indicates that a thread is done */
} pt_state_t;

/**
 * A protothread.
 * This type defines a protothead, which is a low-overhead mechanism for concurrent programming. 
 * Protothreads are stackless, lightweight threads providing a blocking context cheaply with minimal memory overhead.
 * This causes a few limitations:
 *   - They are non-preemptable, so a context switch is possible only on blocking operations.
 *   - Preserving variables across context switches is possible only when using global or static variables.
 */
typedef struct {
	int state;	/** A protothread continuation label. Used to restore a yielded protothread */
	pt_state_t thread_status; /** A protothread status. Determines if the protothread has ended or was blocked */
} pt_t;

/**
 * You should define this variable in your project and increase it periodically.
 * Otherwise sleep functions will not work
 */
extern volatile int jiffies;

/* Helper routine. Allows to implement local continuation in protothreads. */
#define pt_break(pt, pt_state)	\
	do {	\
		(pt)->state = __LINE__;	\
		(pt)->thread_status = pt_state;	\
		return;	\
		case __LINE__:;	\
	} while (0)


/* --------------------------- GENERAL ROUTINES ------------------------- */

/**
 * Protothread initialization. Creates a protothread ready to run
 */
#define pt_init(pt) \
	do {							\
		(pt)->state = 0;	\
		(pt)->thread_status = PT_STATE_BLOCKED;	\
	} while (0)

/**
 * This should be called at the beginning of any protothread function
 */
#define pt_start(pt)	\
	switch ((pt)->state) {	\
		case 0:							\

/**
 * This should be included at the end of any protothread function
 */
#define pt_end(pt)	\
		pt_break(pt, PT_STATE_ENDED);	\
	}	/* end of switch(pt->state) */


/* --------------------------- BLOCKING OPERATIONS ------------------------ */

/**
 * Force prothtorhead to yield execution to another one
 */
#define pt_yield(pt)	\
	pt_break(pt, PT_STATE_BLOCKED)

/**
 * Block until the condition becomes true
 */
#define pt_wait(pt, cond)	\
	do {	\
		pt_break(pt, PT_STATE_BLOCKED);	\
		if (!(cond)) {	\
			return;		\
		}						\
	} while (0)

/**
 * Block while the condition is true
 */
#define pt_wait_while(pt, cond)	\
	pt_wait(pt, !(cond))

/**
 * Same as pt_wait(pt, cont). Used just to make code look more clear.
 */
#define pt_wait_until(pt, cond)	\
	pt_wait(pt, cond)

/**
 * Block thread for a given time.
 * Time is measured in "clock ticks" (relies on jiffies variable - so it's up
 * to you how frequently to increase it).
 */
#define pt_sleep(pt, interval)	\
	do {	\
		static long alarm;	\
		alarm = jiffies + (interval);	\
		pt_wait_until(pt, (alarm < jiffies));	\
	} while (0)

/* ----------------------------- SCHEDULING ------------------------------- */
/* 
 *	TODO: 
 *		o Implement pt_return(pt)
 *		o Implement pt_restart(pt)
 *		o Use a better interface for scheduling protothreads
 */

/**
 * Starts a child protothread and waits until it completes.
 */
#define pt_spawn(pt, pt_child, body)	\
	do {		\
		pt_init(pt_child);	\
		while (pt_schedule(pt_child, body) != PT_STATE_ENDED) {	\
			pt_yield(pt);	\
		}	\
	} while (0)

/**
 * Executes protothread and returns its status
 */
#define pt_schedule(pt, body)	\
	(	\
		body,	\
		(pt)->thread_status	\
	)



/* ----------------------------- IPC -------------------------------- */

/**
 * A simple spinlock (without nested locks)
 */
typedef struct {
	char locked;
} pt_spin_t;

/**
 * Create a spinlock
 */
#define pt_spin_init(s)	\
	(s)->locked = 0;

/**
 * Lock a spinlock
 */
#define pt_spin_lock(pt, s)	\
	do {	\
		pt_wait(pt, (s)->locked == 0);\
		((s)->locked) = 1;\
	} while (0)

/**
 * Unlock a spinlock
 */
#define pt_spin_unlock(pt, s)	\
	((s)->locked) = 0;

#endif /* __AVOS_PT_H_ */
